package linker

import (
	"bytes"
	"debug/elf"
	"encoding/binary"
	"risc-v/ld/pkg/utils"
)

// OutPutELFHeader 输出文件的 ELF 文件头部信息
type OutPutELFHeader struct {
	Chunk
}

// NewOutPutELFHeader 创建 NewOutPutELFHeader 实例
// 执行一些初始化
func NewOutPutELFHeader() *OutPutELFHeader {
	return &OutPutELFHeader{
		//offset 不用设置因为在开头 默认家就是 0
		Chunk{
			SectionHeader: SectionHeader{
				Flags:     uint64(elf.SHF_ALLOC),
				Size:      uint64(FileHeaderSize),
				AddrAlign: 8,
			},
		},
	}
}

func (o *OutPutELFHeader) CopyBuf(ctx *Context) {
	elfHeader := &FileHeader{}
	WriteMagic(elfHeader.Ident[:])

	elfHeader.Ident[elf.EI_CLASS] = uint8(elf.ELFCLASS64) // 写入 ELF 文件类型
	elfHeader.Ident[elf.EI_DATA] = uint8(elf.ELFDATA2LSB) //指定小端流
	elfHeader.Ident[elf.EI_VERSION] = uint8(elf.EV_CURRENT)
	elfHeader.Ident[elf.EI_OSABI] = 0
	elfHeader.Ident[elf.EI_ABIVERSION] = 0

	elfHeader.Type = uint16(elf.ET_EXEC)
	elfHeader.Machine = uint16(elf.EM_RISCV)
	elfHeader.Version = uint32(elf.EV_CURRENT)
	elfHeader.Entry = getEntryAddr(ctx) // 入口第一条地址
	elfHeader.Phoff = ctx.ProgramHeader.SectionHeader.Offset
	elfHeader.Shoff = ctx.SectionHeader.SectionHeader.Offset
	elfHeader.Flags = getFlags(ctx)

	elfHeader.ShEntrySize = uint16(SectionHeaderSize)
	elfHeader.PhEntrySize = uint16(ProgramHeaderSize)

	elfHeader.Ehsize = uint16(FileHeaderSize)

	elfHeader.ShNum = uint16(ctx.SectionHeader.SectionHeader.Size) / uint16(SectionHeaderSize)
	elfHeader.PhNum = uint16(ctx.ProgramHeader.SectionHeader.Size) / uint16(ProgramHeaderSize)

	buf := &bytes.Buffer{}
	err := binary.Write(buf, binary.LittleEndian, elfHeader)
	utils.MustNo(err)

	copy(ctx.Buf[o.SectionHeader.Offset:], buf.Bytes())
}

// 第一个 .text 节的入口地址
func getEntryAddr(ctx *Context) uint64 {
	for _, osection := range ctx.OutPutSections {
		if osection.Name == ".text" {
			return osection.SectionHeader.Addr
		}
	}

	return 0
}

func getFlags(ctx *Context) uint32 {
	utils.Assert(len(ctx.ObjectFiles) > 0)

	flags := ctx.ObjectFiles[0].GetELFHeader().Flags
	for _, objectFile := range ctx.ObjectFiles[1:] {

		if objectFile.GetELFHeader().Flags&EF_RISCV_RVC != 0 {
			flags |= EF_RISCV_RVC
			break
		}
	}

	return flags
}
